feat(ui): add keytrace-style identity visualizer (poc)#2616
feat(ui): add keytrace-style identity visualizer (poc)#2616BittuBarnwal7479 wants to merge 10 commits intonpmx-dev:mainfrom
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
2 Skipped Deployments
|
📝 WalkthroughWalkthroughAdds Keytrace integration: new Vue components (ProfileHeader, LinkedAccounts, AccountItem), a composable to fetch/sort profiles, two server API endpoints (/api/keytrace/[domain], /api/keytrace/reverify), TypeScript Keytrace types, i18n schema and locale updates, and accessibility tests. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant ProfilePage as Profile Page
participant useKeytraceProfile as useKeytraceProfile
participant API as /api/keytrace/[domain]
participant ProfileHeader as ProfileHeader
participant LinkedAccounts as LinkedAccounts
User->>ProfilePage: Navigate to /profile/[identity]
ProfilePage->>useKeytraceProfile: call(domain)
useKeytraceProfile->>API: fetch(domain)
API-->>useKeytraceProfile: KeytraceResponse(profile, accounts)
useKeytraceProfile-->>ProfilePage: profile, sortedAccounts, loading
ProfilePage->>ProfileHeader: pass(profile, loading)
ProfilePage->>LinkedAccounts: pass(accounts, loading)
ProfileHeader-->>User: render header (avatar, name, desc)
LinkedAccounts-->>User: render account list & legend
sequenceDiagram
actor User
participant AccountItem as AccountItem
participant ReverifyAPI as /api/keytrace/reverify
participant Account as Account (state)
User->>AccountItem: Click "Re-verify"
AccountItem->>AccountItem: start tooltip + 4-step progress
AccountItem->>ReverifyAPI: POST { identity, platform, username, url? }
ReverifyAPI-->>AccountItem: KeytraceReverifyResponse(status, lastCheckedAt, failureReason?)
AccountItem->>Account: update local status, lastCheckedAt, failureReason
Account-->>User: updated badge/timestamps shown
Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Lunaria Status Overview🌕 This pull request will trigger status changes. Learn moreBy default, every PR changing files present in the Lunaria configuration's You can change this by adding one of the keywords present in the Tracked Files
Warnings reference
|
There was a problem hiding this comment.
Actionable comments posted: 10
🧹 Nitpick comments (4)
CONTRIBUTING.md (1)
351-351: Documentation update correctly reflects the routing change.Swapping the
unplugin-vue-routerreference for Nuxt's experimentaltypedPagesoption is consistent with the dependency changes inpackage.json. Minor nit:typedPagesis flagged as experimental in Nuxt — it may be worth a brief parenthetical note so contributors know the type-safety guarantee depends on that flag being enabled innuxt.config.ts.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@CONTRIBUTING.md` at line 351, Update the CONTRIBUTING.md note to mention that Nuxt's typedPages option is experimental and that the type-safety guarantees for using object-syntax named routes depend on enabling typedPages in nuxt.config.ts; specifically, add a short parenthetical after the sentence that recommends "object syntax with named routes" referencing the experimental nature of the typedPages option and instructing contributors to enable typedPages in nuxt.config.ts to get the typed route benefits..github/workflows/ci.yml (1)
117-118: Use$GITHUB_WORKSPACEinstead of hard-coding the Actions workspace path.Line 118 bakes in the current repository path. Using the runtime variable maintains compatibility if the repository name or checkout location changes.
Proposed change
- name: 👑 Fix Git ownership - run: git config --global --add safe.directory /__w/npmx.dev/npmx.dev + run: git config --global --add safe.directory "$GITHUB_WORKSPACE"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/ci.yml around lines 117 - 118, Replace the hard-coded path in the "👑 Fix Git ownership" step by using the GitHub Actions runtime variable: update the git config --global --add safe.directory command to use $GITHUB_WORKSPACE instead of the literal "/__w/npmx.dev/npmx.dev" so the safe.directory is set dynamically for the checked-out repo.app/components/LinkedAccounts.vue (1)
4-9: Legend colours bypass the app's design tokens.Other components in the app style via semantic tokens (
bg-bg-subtle,text-fg-muted,border-border) so they respond to accent/background theme settings. The raw palette utilities here (bg-emerald-500/15,text-yellow-300, etc.) will look identical across themes and may clash on light backgrounds. Consider introducing status-specific semantic classes or at minimum verifying contrast on both light and dark themes.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/components/LinkedAccounts.vue` around lines 4 - 9, The statusLegend array uses raw Tailwind color utilities (statusLegend, className) which bypass the app's semantic design tokens; replace these hardcoded palette classes with semantic token-based classes (e.g., create status-specific utility classes like .status-verified, .status-unverified, .status-stale, .status-failed that map to tokens such as bg-bg-subtle / text-fg-muted / border-border or token variants for each status) and update statusLegend.className to reference those semantic classes so the badges respect theme/accent settings and verify contrast in both light and dark themes.app/composables/useKeytraceProfile.ts (1)
1-1: Rely on the project’s shared-type auto-imports here.This composable is under
app/composables, so the explicit#shared/types/keytraceimport duplicates the repo’s Nuxt auto-import convention forshared/types/*.♻️ Proposed cleanup
-import type { KeytraceAccount, KeytraceResponse } from '#shared/types/keytrace' -Based on learnings, “exports from shared/types/* … are auto-imported by Nuxt for composables and components. Do not add explicit import statements … in files under app/composables”.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/composables/useKeytraceProfile.ts` at line 1, The explicit import of KeytraceAccount and KeytraceResponse at the top of useKeytraceProfile.ts duplicates the repo's Nuxt auto-imports for shared/types in composables; remove the line "import type { KeytraceAccount, KeytraceResponse } from '#shared/types/keytrace'" and rely on the project-wide auto-imported types (references to KeytraceAccount and KeytraceResponse inside the useKeytraceProfile composable should continue to work without the explicit import).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/components/AccountItem.vue`:
- Around line 202-206: getStepState currently treats a step as 'active' when
currentVerificationStep === stepIndex AND (isReverifying OR reverifyError),
which keeps the spinner running after a failure; update the condition in
getStepState (and the duplicate logic around the other block) to require
isReverifying to be true and reverifyError to be false (e.g., use
isReverifying.value && !reverifyError.value alongside the
currentVerificationStep/stepIndex check) so the step becomes non-active when a
reverifyError is set.
- Around line 165-175: The responsePromise created from $fetch (typed
KeytraceReverifyResponse) may reject while the subsequent runStep() delay calls
execute, causing unhandled rejection warnings; attach a rejection handler
immediately by calling Promise.allSettled([responsePromise]) (or otherwise
attach .catch) before invoking runStep(0..3), then await the original
responsePromise later as before so your existing try/catch still handles the
outcome; update the code around responsePromise and the runStep sequence in
AccountItem.vue accordingly.
In `@app/components/Compare/FacetSelector.vue`:
- Around line 108-113: The hover styles conflict between the static class
attribute and the conditional :class for facet.comingSoon, causing enabled hover
utilities (hover:(text-fg-muted border-border)) to potentially override the
muted hover (hover:(text-fg-subtle/50 border-border-subtle)); fix by moving
hover:(text-fg-muted border-border) out of the base class and into the
non-coming-soon branch of the :class binding (so only the enabled branch adds
enabled hover utilities), or alternatively replace with a disabled: modifier
pattern consistent with Button/Base.vue and Select/Base.vue so coming-soon chips
only receive the muted hover styles via facet.comingSoon conditional logic.
In `@app/components/LinkedAccounts.vue`:
- Around line 22-49: Replace hardcoded UI strings in the LinkedAccounts.vue
template with i18n lookups: use $t('profile.linked_accounts.title') for "Linked
Accounts"; replace the verified summary line that uses
verifiedCount/accounts.length with a translated key like
$t('profile.linked_accounts.verified_summary', { verified: verifiedCount, total:
accounts.length }); map each entry in statusLegend to use
$t('profile.linked_accounts.status.<key>') for the four legend labels and use
$t('profile.linked_accounts.legend_label') for the container aria-label; replace
"No linked accounts" with $t('profile.linked_accounts.empty'); ensure you update
the template expressions around verifiedCount, accounts, statusLegend, loading,
SkeletonBlock and AccountItem to call $t(...) consistently with the new
profile.* keys added to i18n/locales/*.json.
In `@app/components/ProfileHeader.vue`:
- Around line 53-67: Replace the hardcoded English strings in ProfileHeader.vue
with i18n lookups: change the alt fallback "Profile avatar" and the name
fallback "Unknown Profile" to use this.$t (or $t) with dedicated keys (e.g.
profile.avatarAlt and profile.unknown) so they respect the active locale; update
any template references that use profile.name || 'Unknown Profile' and
:alt="profile.name || 'Profile avatar'" to use the translated fallbacks, and add
corresponding keys under the profile.* namespace in the translation files to
match the project's i18n pattern.
In `@app/pages/profile/`[identity]/index.vue:
- Around line 101-105: The Keytrace UI (ProfileHeader and LinkedAccounts) is
being rendered for all identity routes because useKeytraceProfile(identity) is
invoked unconditionally and the mock API returns synthesized profiles for
unknown domains; update the rendering to gate the Keytrace-related components by
detecting domain-like identities (e.g. a simple regex check on identity) or by
checking a real Keytrace signal before rendering: call
useKeytraceProfile(identity) only when identity matches a domain pattern (or add
a boolean like isDomainFromIdentity) and wrap ProfileHeader and LinkedAccounts
with that condition (references: useKeytraceProfile, ProfileHeader,
LinkedAccounts, identity) so non-domain handles do not show fabricated linked
accounts.
In `@i18n/locales/de-AT.json`:
- Around line 27-28: The strings for keys "likes_error" and "likes_empty"
currently say "Beliebte Pakete" (popular packages) but should refer to the
user's liked packages; update the translations for likes_error and likes_empty
so they use a German phrase meaning "liked packages" (e.g., use a localized term
such as "Gemerkte Pakete" or "Gelikte Pakete") to correctly reflect the
profile's liked-packages section.
In `@i18n/locales/te-IN.json`:
- Around line 126-128: The three Telugu locale entries
"public_interests_description", "likes_error", and "likes_empty" are still in
English; update their values to proper Telugu translations (replace the English
strings with Telugu equivalents) or remove these keys from te-IN.json until
translations are available so Telugu users don't see English UI; locate these
keys in the i18n/locales/te-IN.json file and either provide accurate Telugu text
for public_interests_description, likes_error, and likes_empty or revert/delete
them to keep the locale as a placeholder.
In `@server/api/keytrace/reverify.post.ts`:
- Around line 21-26: The current reverify handler always returns a
KeytraceReverifyResponse with status: 'unverified', which will incorrectly
downgrade verified accounts; update the handler that builds the response (the
code creating the response object in reverify.post) to preserve the account's
current status instead of hardcoding 'unverified' — e.g., read the incoming
request's currentStatus (or fetch the account's status from the same source the
UI uses) and set response.status = currentStatus, adjust failureReason to
null/empty when status is unchanged, and only set failureReason and 'unverified'
if a real verification attempt fails.
In `@test/e2e/interactions.spec.ts`:
- Around line 293-310: The test '"s" does not navigate when any modifier key is
pressed' is ambiguous because it focuses '#header-search' causing typed
modifiers like 'Shift+s' to insert text and potentially trigger navigation;
remove the focus and the subsequent expect(searchInput).toBeFocused() lines from
this test so it mirrors the '"c"' modifier test (no editable focused) and
reliably asserts the URL stays on /settings; if you need coverage for shortcuts
being suppressed while an editable is focused, add a separate test named e.g.
'"s" is suppressed while editable is focused' that focuses '#header-search' and
verifies an unmodified 's' does not trigger navigation.
---
Nitpick comments:
In @.github/workflows/ci.yml:
- Around line 117-118: Replace the hard-coded path in the "👑 Fix Git ownership"
step by using the GitHub Actions runtime variable: update the git config
--global --add safe.directory command to use $GITHUB_WORKSPACE instead of the
literal "/__w/npmx.dev/npmx.dev" so the safe.directory is set dynamically for
the checked-out repo.
In `@app/components/LinkedAccounts.vue`:
- Around line 4-9: The statusLegend array uses raw Tailwind color utilities
(statusLegend, className) which bypass the app's semantic design tokens; replace
these hardcoded palette classes with semantic token-based classes (e.g., create
status-specific utility classes like .status-verified, .status-unverified,
.status-stale, .status-failed that map to tokens such as bg-bg-subtle /
text-fg-muted / border-border or token variants for each status) and update
statusLegend.className to reference those semantic classes so the badges respect
theme/accent settings and verify contrast in both light and dark themes.
In `@app/composables/useKeytraceProfile.ts`:
- Line 1: The explicit import of KeytraceAccount and KeytraceResponse at the top
of useKeytraceProfile.ts duplicates the repo's Nuxt auto-imports for
shared/types in composables; remove the line "import type { KeytraceAccount,
KeytraceResponse } from '#shared/types/keytrace'" and rely on the project-wide
auto-imported types (references to KeytraceAccount and KeytraceResponse inside
the useKeytraceProfile composable should continue to work without the explicit
import).
In `@CONTRIBUTING.md`:
- Line 351: Update the CONTRIBUTING.md note to mention that Nuxt's typedPages
option is experimental and that the type-safety guarantees for using
object-syntax named routes depend on enabling typedPages in nuxt.config.ts;
specifically, add a short parenthetical after the sentence that recommends
"object syntax with named routes" referencing the experimental nature of the
typedPages option and instructing contributors to enable typedPages in
nuxt.config.ts to get the typed route benefits.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: b8efc060-b9db-4969-8d02-e8ed9e9c58f0
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (54)
.github/workflows/ci.ymlCONTRIBUTING.mdapp/components/AccountItem.vueapp/components/Compare/FacetSelector.vueapp/components/LinkedAccounts.vueapp/components/ProfileHeader.vueapp/composables/useConnector.tsapp/composables/useKeytraceProfile.tsapp/pages/profile/[identity]/index.vuedocs/package.jsoni18n/locales/ar-EG.jsoni18n/locales/ar.jsoni18n/locales/az-AZ.jsoni18n/locales/bg-BG.jsoni18n/locales/bn-IN.jsoni18n/locales/cs-CZ.jsoni18n/locales/de-AT.jsoni18n/locales/de-DE.jsoni18n/locales/de.jsoni18n/locales/en-GB.jsoni18n/locales/en.jsoni18n/locales/es-419.jsoni18n/locales/es.jsoni18n/locales/fr-FR.jsoni18n/locales/hi-IN.jsoni18n/locales/hu-HU.jsoni18n/locales/id-ID.jsoni18n/locales/it-IT.jsoni18n/locales/ja-JP.jsoni18n/locales/kn-IN.jsoni18n/locales/mr-IN.jsoni18n/locales/nb-NO.jsoni18n/locales/ne-NP.jsoni18n/locales/nl.jsoni18n/locales/pl-PL.jsoni18n/locales/pt-BR.jsoni18n/locales/ru-RU.jsoni18n/locales/sr-Latn-RS.jsoni18n/locales/ta-IN.jsoni18n/locales/te-IN.jsoni18n/locales/tr-TR.jsoni18n/locales/uk-UA.jsoni18n/locales/vi-VN.jsoni18n/locales/zh-CN.jsoni18n/locales/zh-TW.jsoni18n/schema.jsonknip.tslighthouse-setup.cjspackage.jsonpnpm-workspace.yamlserver/api/keytrace/[domain].tsserver/api/keytrace/reverify.post.tsshared/types/keytrace.tstest/e2e/interactions.spec.ts
💤 Files with no reviewable changes (1)
- knip.ts
| "public_interests_description": "Public package interests and activity signals.", | ||
| "likes_error": "Could not load liked packages.", | ||
| "likes_empty": "No liked packages yet." |
There was a problem hiding this comment.
Translate these Telugu locale strings or leave them unset.
These new te-IN values are still in English, so Telugu users will see untranslated profile UI. Please translate them to Telugu, or omit the keys until a translation is available.
🌐 Alternative if Telugu translations are not ready
- "invite": {},
- "public_interests_description": "Public package interests and activity signals.",
- "likes_error": "Could not load liked packages.",
- "likes_empty": "No liked packages yet."
+ "invite": {}Based on learnings, empty placeholder objects in non-English locale files are expected, but non-placeholder value changes that look manually edited should still be reviewed.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@i18n/locales/te-IN.json` around lines 126 - 128, The three Telugu locale
entries "public_interests_description", "likes_error", and "likes_empty" are
still in English; update their values to proper Telugu translations (replace the
English strings with Telugu equivalents) or remove these keys from te-IN.json
until translations are available so Telugu users don't see English UI; locate
these keys in the i18n/locales/te-IN.json file and either provide accurate
Telugu text for public_interests_description, likes_error, and likes_empty or
revert/delete them to keep the locale as a placeholder.
Co-authored-by: Copilot <copilot@github.com>
d142652 to
5043d74
Compare
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
i18n/locales/en.json (1)
305-349:⚠️ Potential issue | 🟡 MinorKeep
common.errorwhile it is still referenced.
app/storybook/mocks/handlers/lunaria-status.tsstill referencescommon.error; removing it from the source locale will surface a missing translation key there unless the consumer is updated.Proposed fix
"common": { + "error": "Error", "loading": "Loading...",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@i18n/locales/en.json` around lines 305 - 349, Restore the removed translation key by re-adding "error" under the "common" object in the locale (so common.error exists) with an appropriate string (e.g., "Error") because app/storybook/mocks/handlers/lunaria-status.ts still references common.error; alternatively update that handler to use an existing key if you intend to remove common.error, but do one of these so the missing translation key is resolved.i18n/locales/de.json (1)
300-344:⚠️ Potential issue | 🟠 MajorRestore
common.errorfor existing translation consumers.
app/storybook/mocks/handlers/lunaria-status.ts:30-50still requestscommon.error, so deleting the shared key risks missing translations in that mock path.Proposed fix
"common": { "loading": "Lädt...", "loading_more": "Lädt mehr...", "loading_packages": "Pakete werden geladen...", "end_of_results": "Keine weiteren Ergebnisse", "try_again": "Erneut versuchen", + "error": "Fehler", "close": "Schließen",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@i18n/locales/de.json` around lines 300 - 344, Restore the removed "common.error" key in the German locale so existing consumers (e.g., the lunaria-status mock that references common.error) continue to find a translation; add an appropriate German string like "Fehler" or a full sentence under the "common" object with the key "error" to mirror other locales, ensuring the key name is exactly "common.error" so code paths referencing common.error (such as the handler in app/storybook/mocks/handlers/lunaria-status.ts) work without changes.i18n/locales/ja-JP.json (1)
171-210:⚠️ Potential issue | 🟠 MajorRestore
common.errorfor existing translation consumers.
app/storybook/mocks/handlers/lunaria-status.ts:30-50still includescommon.error; removing it from this locale can make the mock translation payload fail or render a missing key.Proposed fix
"common": { "loading": "読み込み中...", "loading_more": "さらに読み込み中...", "loading_packages": "パッケージを読み込み中...", "end_of_results": "結果の最後です", "try_again": "もう一度お試しください", + "error": "エラー", "close": "閉じる",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@i18n/locales/ja-JP.json` around lines 171 - 210, The ja-JP locale removed the common.error key which breaks consumers expecting it (storybook mock in app/storybook/mocks/handlers/lunaria-status.ts references common.error); restore the common.error string in the "common" object of i18n/locales/ja-JP.json with an appropriate Japanese translation (e.g., "エラーが発生しました") so the mock translation payload and any runtime lookups find the key.
🧹 Nitpick comments (2)
test/nuxt/a11y.spec.ts (1)
910-968: Consider expanding state coverage for the new a11y suites.Current tests only exercise the "happy path" (populated profile, single verified GitHub account). Since
ProfileHeaderhas distinct avatar/initials fallback branches andLinkedAccounts/AccountItemrender differently perstatus(verified/unverified/stale/failed) andloading, adding a few more cases would catch real accessibility regressions (e.g. missingalton the fallback avatar, colour-contrast issues on non-verified status badges, skeleton/loading ARIA).Suggested additional cases:
ProfileHeader:loading: true, and a profile with noavatar(initials fallback).LinkedAccounts: emptyaccounts: [],loading: true, and a mix containingunverified/stale/failedstatuses.AccountItem: one test per non-verifiedstatus.Not blocking for the POC, but cheap to add here so the audit actually covers the new visual states.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/nuxt/a11y.spec.ts` around lines 910 - 968, Add a few more a11y test cases to exercise non-happy-path states: for ProfileHeader add tests for loading: true and for a profile without an avatar (initials fallback) using mountSuspended(ProfileHeader) and runAxe; for LinkedAccounts add tests for accounts: [] (empty list), loading: true, and a mixed list containing statuses 'unverified', 'stale', and 'failed'; for AccountItem add separate tests rendering AccountItem for each non-verified status ('unverified','stale','failed') and run runAxe against each mounted component to assert no violations. Ensure each test uses the same mountSuspended/props pattern so the axe audit covers fallback avatar, status badges, and loading skeleton ARIA states.i18n/locales/de-AT.json (1)
24-43: Avoid duplicating base German strings in the Austrian variant.These
profileentries are identical tode.json, sode-AT.jsoncan inherit them from the base locale and only override Austria-specific wording.Proposed cleanup
- "search": { + "search": { "instant_search": "Schnellsuche", "instant_search_on": "Schnellsuche aktiviert", "instant_search_off": "Schnellsuche deaktiviert", "instant_search_turn_on": "Schnellsuche aktivieren", "instant_search_turn_off": "Schnellsuche deaktivieren", "instant_search_advisory": "Die Schnellsuche sendet bei jedem Tastendruck eine Anfrage." - }, - "profile": { - "public_interests_description": "Öffentliche Paketinteressen und Aktivitätssignale.", - "likes_error": "Gelikte Pakete konnten nicht geladen werden.", - "likes_empty": "Noch keine gelikten Pakete.", - "avatar_alt": "Profilavatar", - "unknown_profile": "Unbekanntes Profil", - "linked_accounts": { - "status": { - "verified": "Verifiziert", - "unverified": "Nicht verifiziert", - "stale": "Veraltet", - "failed": "Fehlgeschlagen" - }, - "title": "Verknüpfte Konten", - "verified_summary": "Verifizierte Konten", - "legend_aria_label": "Legende zum Verifizierungsstatus von Konten", - "empty": "Keine verknüpften Konten" - } }Based on learnings, “when using country locale variants (e.g., es-419, es-ES), place only translations that differ from the base language in variant JSON files.”
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@i18n/locales/de-AT.json` around lines 24 - 43, The de-AT locale duplicates the entire "profile" block from the base German locale; remove the redundant keys under "profile" in de-AT.json and leave only Austria-specific overrides (if none, remove the "profile" object entirely) so the app falls back to de.json for shared strings; target the "profile" object and its nested keys like "public_interests_description", "likes_error", "likes_empty", "avatar_alt", "unknown_profile", and the "linked_accounts" block when pruning duplicates.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@i18n/locales/de-DE.json`:
- Around line 16-17: The German locale entry "verified_summary" currently is a
static label and must include the interpolation placeholders used by
app/components/LinkedAccounts.vue; update the "verified_summary" value in
i18n/locales/de-DE.json to include the {verified} and {total} placeholders (e.g.
a German phrase using {verified} and {total}) so the component's passed values
are rendered in the UI.
In `@i18n/locales/en.json`:
- Around line 378-379: The "verified_summary" translation currently returns a
static label; update the en.json "verified_summary" value to include
interpolation placeholders for the counts (e.g., {verified} and {total}) so the
string uses the { verified, total } values passed from
app/components/LinkedAccounts.vue; keep the placeholder names matching what
LinkedAccounts.vue supplies and preserve pluralization/formatting conventions
used by your i18n setup.
In `@i18n/schema.json`:
- Around line 1114-1130: The AccountItem.vue component is still using hardcoded
English labels for account status; update it to read the new i18n keys under
profile.linked_accounts.status (e.g. profile.linked_accounts.status.verified,
.unverified, .stale, .failed) instead of literal strings. Locate the status
display logic in AccountItem.vue (the template or computed that currently
returns "Verified", "Unverified", "Stale", "Failed") and replace those literals
with calls to the translation helper (e.g. this.$t or $t in the template) so the
UI uses profile.linked_accounts.status.* keys for all four statuses. Ensure any
mapping logic (status code -> label) uses the translation keys consistently and
falls back to a safe default if a key is missing.
---
Outside diff comments:
In `@i18n/locales/de.json`:
- Around line 300-344: Restore the removed "common.error" key in the German
locale so existing consumers (e.g., the lunaria-status mock that references
common.error) continue to find a translation; add an appropriate German string
like "Fehler" or a full sentence under the "common" object with the key "error"
to mirror other locales, ensuring the key name is exactly "common.error" so code
paths referencing common.error (such as the handler in
app/storybook/mocks/handlers/lunaria-status.ts) work without changes.
In `@i18n/locales/en.json`:
- Around line 305-349: Restore the removed translation key by re-adding "error"
under the "common" object in the locale (so common.error exists) with an
appropriate string (e.g., "Error") because
app/storybook/mocks/handlers/lunaria-status.ts still references common.error;
alternatively update that handler to use an existing key if you intend to remove
common.error, but do one of these so the missing translation key is resolved.
In `@i18n/locales/ja-JP.json`:
- Around line 171-210: The ja-JP locale removed the common.error key which
breaks consumers expecting it (storybook mock in
app/storybook/mocks/handlers/lunaria-status.ts references common.error); restore
the common.error string in the "common" object of i18n/locales/ja-JP.json with
an appropriate Japanese translation (e.g., "エラーが発生しました") so the mock translation
payload and any runtime lookups find the key.
---
Nitpick comments:
In `@i18n/locales/de-AT.json`:
- Around line 24-43: The de-AT locale duplicates the entire "profile" block from
the base German locale; remove the redundant keys under "profile" in de-AT.json
and leave only Austria-specific overrides (if none, remove the "profile" object
entirely) so the app falls back to de.json for shared strings; target the
"profile" object and its nested keys like "public_interests_description",
"likes_error", "likes_empty", "avatar_alt", "unknown_profile", and the
"linked_accounts" block when pruning duplicates.
In `@test/nuxt/a11y.spec.ts`:
- Around line 910-968: Add a few more a11y test cases to exercise non-happy-path
states: for ProfileHeader add tests for loading: true and for a profile without
an avatar (initials fallback) using mountSuspended(ProfileHeader) and runAxe;
for LinkedAccounts add tests for accounts: [] (empty list), loading: true, and a
mixed list containing statuses 'unverified', 'stale', and 'failed'; for
AccountItem add separate tests rendering AccountItem for each non-verified
status ('unverified','stale','failed') and run runAxe against each mounted
component to assert no violations. Ensure each test uses the same
mountSuspended/props pattern so the axe audit covers fallback avatar, status
badges, and loading skeleton ARIA states.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: ab559b41-ee63-4e79-a588-a0829e9bca5d
📒 Files selected for processing (46)
app/components/AccountItem.vueapp/components/Compare/FacetSelector.vueapp/components/LinkedAccounts.vueapp/components/ProfileHeader.vueapp/composables/useKeytraceProfile.tsapp/pages/profile/[identity]/index.vuei18n/locales/ar-EG.jsoni18n/locales/ar.jsoni18n/locales/az-AZ.jsoni18n/locales/bg-BG.jsoni18n/locales/bn-IN.jsoni18n/locales/cs-CZ.jsoni18n/locales/de-AT.jsoni18n/locales/de-DE.jsoni18n/locales/de.jsoni18n/locales/en-GB.jsoni18n/locales/en.jsoni18n/locales/es-419.jsoni18n/locales/es.jsoni18n/locales/fr-FR.jsoni18n/locales/hi-IN.jsoni18n/locales/hu-HU.jsoni18n/locales/id-ID.jsoni18n/locales/it-IT.jsoni18n/locales/ja-JP.jsoni18n/locales/kn-IN.jsoni18n/locales/mr-IN.jsoni18n/locales/nb-NO.jsoni18n/locales/ne-NP.jsoni18n/locales/nl.jsoni18n/locales/pl-PL.jsoni18n/locales/pt-BR.jsoni18n/locales/ru-RU.jsoni18n/locales/sr-Latn-RS.jsoni18n/locales/ta-IN.jsoni18n/locales/te-IN.jsoni18n/locales/tr-TR.jsoni18n/locales/uk-UA.jsoni18n/locales/vi-VN.jsoni18n/locales/zh-CN.jsoni18n/locales/zh-TW.jsoni18n/schema.jsonserver/api/keytrace/[domain].tsserver/api/keytrace/reverify.post.tsshared/types/keytrace.tstest/nuxt/a11y.spec.ts
✅ Files skipped from review due to trivial changes (10)
- i18n/locales/kn-IN.json
- i18n/locales/it-IT.json
- i18n/locales/te-IN.json
- i18n/locales/en-GB.json
- i18n/locales/ta-IN.json
- i18n/locales/es-419.json
- i18n/locales/id-ID.json
- app/components/LinkedAccounts.vue
- shared/types/keytrace.ts
- app/pages/profile/[identity]/index.vue
🚧 Files skipped from review as they are similar to previous changes (23)
- i18n/locales/bn-IN.json
- i18n/locales/ne-NP.json
- i18n/locales/es.json
- i18n/locales/nl.json
- i18n/locales/ru-RU.json
- i18n/locales/pt-BR.json
- i18n/locales/mr-IN.json
- i18n/locales/fr-FR.json
- i18n/locales/hi-IN.json
- i18n/locales/zh-CN.json
- i18n/locales/pl-PL.json
- i18n/locales/tr-TR.json
- i18n/locales/ar-EG.json
- server/api/keytrace/reverify.post.ts
- app/components/Compare/FacetSelector.vue
- server/api/keytrace/[domain].ts
- app/components/ProfileHeader.vue
- i18n/locales/az-AZ.json
- i18n/locales/bg-BG.json
- app/components/AccountItem.vue
- i18n/locales/hu-HU.json
- i18n/locales/cs-CZ.json
- i18n/locales/uk-UA.json
| "title": "Verknüpfte Konten", | ||
| "verified_summary": "Verifizierte Konten", |
There was a problem hiding this comment.
Render the verified-account counts in the summary.
Line 17 ignores the { verified, total } values passed by app/components/LinkedAccounts.vue, so the German UI will only show a static label instead of the verification summary.
Proposed fix
- "verified_summary": "Verifizierte Konten",
+ "verified_summary": "{verified}/{total} verifiziert",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@i18n/locales/de-DE.json` around lines 16 - 17, The German locale entry
"verified_summary" currently is a static label and must include the
interpolation placeholders used by app/components/LinkedAccounts.vue; update the
"verified_summary" value in i18n/locales/de-DE.json to include the {verified}
and {total} placeholders (e.g. a German phrase using {verified} and {total}) so
the component's passed values are rendered in the UI.
| "title": "Linked Accounts", | ||
| "verified_summary": "Verified accounts", |
There was a problem hiding this comment.
Include the verified-account counts in the summary string.
Line 379 ignores the { verified, total } values passed by app/components/LinkedAccounts.vue, so the linked-accounts header will only render a static label.
Proposed fix
- "verified_summary": "Verified accounts",
+ "verified_summary": "{verified}/{total} verified",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "title": "Linked Accounts", | |
| "verified_summary": "Verified accounts", | |
| "title": "Linked Accounts", | |
| "verified_summary": "{verified}/{total} verified", |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@i18n/locales/en.json` around lines 378 - 379, The "verified_summary"
translation currently returns a static label; update the en.json
"verified_summary" value to include interpolation placeholders for the counts
(e.g., {verified} and {total}) so the string uses the { verified, total } values
passed from app/components/LinkedAccounts.vue; keep the placeholder names
matching what LinkedAccounts.vue supplies and preserve pluralization/formatting
conventions used by your i18n setup.
| "status": { | ||
| "type": "object", | ||
| "properties": { | ||
| "verified": { | ||
| "type": "string" | ||
| }, | ||
| "unverified": { | ||
| "type": "string" | ||
| }, | ||
| "stale": { | ||
| "type": "string" | ||
| }, | ||
| "failed": { | ||
| "type": "string" | ||
| } | ||
| }, | ||
| "additionalProperties": false |
There was a problem hiding this comment.
Wire these status translations into the account item UI.
The schema now exposes profile.linked_accounts.status.*, but app/components/AccountItem.vue:38-50 still uses hardcoded English labels. Non-English profile pages will keep showing Verified, Unverified, Stale, and Failed unless the component reads these translation keys.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@i18n/schema.json` around lines 1114 - 1130, The AccountItem.vue component is
still using hardcoded English labels for account status; update it to read the
new i18n keys under profile.linked_accounts.status (e.g.
profile.linked_accounts.status.verified, .unverified, .stale, .failed) instead
of literal strings. Locate the status display logic in AccountItem.vue (the
template or computed that currently returns "Verified", "Unverified", "Stale",
"Failed") and replace those literals with calls to the translation helper (e.g.
this.$t or $t in the template) so the UI uses profile.linked_accounts.status.*
keys for all four statuses. Ensure any mapping logic (status code -> label) uses
the translation keys consistently and falls back to a safe default if a key is
missing.
…keytrace files Co-authored-by: Copilot <copilot@github.com>
e18e dependency analysisNo dependency warnings found. |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (1)
app/pages/profile/[identity]/index.vue (1)
242-249:⚠️ Potential issue | 🟡 MinorKeytrace section still renders for non-domain identities.
useKeytraceProfile(identity)is invoked unconditionally, and the mock/real endpoint returns a synthesised profile for any handle. Non-domain identities (e.g. atproto handles) will still display a "Linked Accounts" block. Please gate the<section>on a domain-like check (or on a real Keytrace signal once wired), as already noted in the previous review round.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/pages/profile/`[identity]/index.vue around lines 242 - 249, The Keytrace block (ProfileHeader + LinkedAccounts) is rendered unconditionally because useKeytraceProfile(identity) returns fake profiles for non-domain handles; wrap that <section> in a domain-check so it only renders for domain-like identities. Add a small computed or helper (e.g., isDomainIdentity(identity) or computed property isDomain) that checks identity format (e.g., contains a dot and not an at-prefix, or use a regex like /^[^@]+\.[^@]+$/) and replace the unconditional section with <section v-if="isDomain">…</section> (keeping existing props keytraceProfile, keytraceLoading, keytraceAccounts and the useKeytraceProfile call intact). Ensure the symbol names referenced are isDomain (or isDomainIdentity), useKeytraceProfile, ProfileHeader, and LinkedAccounts so reviewers can locate the change.
🧹 Nitpick comments (3)
app/components/AccountItem.vue (2)
218-230:formatDateis locale-locked toen-US.Even once strings are translated, dates will render in
en-USform for every user. Read the active locale fromuseI18n()(or pass it in) so the formatter follows the user's language.♻️ Proposed fix
-function formatDate(value: string): string { +const { locale } = useI18n() + +function formatDate(value: string): string { const date = new Date(value) if (Number.isNaN(date.getTime())) { - return 'Unknown date' + return $t('profile.linked_accounts.unknown_date') } - return new Intl.DateTimeFormat('en-US', { + return new Intl.DateTimeFormat(locale.value, { month: 'short', day: 'numeric', year: 'numeric', timeZone: 'UTC', }).format(date) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/components/AccountItem.vue` around lines 218 - 230, The formatDate function is hardcoded to 'en-US'; update it to use the runtime locale from useI18n() (or accept a locale parameter) so dates render per user language: inside formatDate (or its caller) obtain the active locale from useI18n().locale.value and pass that to new Intl.DateTimeFormat instead of 'en-US', keep the same options (month/day/year/timeZone) and preserve the NaN check and 'Unknown date' return; reference function name formatDate and the i18n hook useI18n to locate where to change the code.
14-44: User-visible copy and labels should go through i18n.The project uses
nuxtjs/i18n(and this PR is updating locale files per the AI summary), but this component hard-codes every user-visible string:platformLabelMap,proofMethodLabelMap,statusLabelMap,verificationSteps,"Unknown date","Re-verification failed: …","Checking...","Re-verify","Re-verify Claim","via {method}","Added","Last checked". Non-English locales will see English-only output, and translations will require a second pass over the component.Please route all displayed strings through
$t()/useI18nand move the copy into the locale schema alongside the keys the sibling components already register.Also applies to: 75-80, 187-191, 218-230, 307-318
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/components/AccountItem.vue` around lines 14 - 44, This component hardcodes all user-visible copy (see platformLabelMap, platformIconMap, proofMethodLabelMap, statusLabelMap, verificationSteps and literal strings like "Unknown date", "Re-verification failed: …", "Checking...", "Re-verify", "Re-verify Claim", "via {method}", "Added", "Last checked"); update the component to use nuxt-i18n (useI18n or this.$t) for every displayed label and message, replace the map values and hard-coded strings with translation keys (e.g. account.platform.github, account.proofMethod.pgp, account.status.verified, account.actions.reverify, account.messages.unknownDate, etc.), and add those keys to the locale schema/JSON used by sibling components so translations exist for all locales; keep platformIconMap as-is (icons are not user copy) but ensure any user-facing text derived from those maps uses translated labels.server/api/keytrace/[domain].ts (1)
194-195: Stale comment — no "mock mode" branch exists in this handler.The comment references a mock-mode toggle that is not implemented. Either drop the reference or wire the toggle back in so the code matches the intent.
♻️ Proposed cleanup
- // If Keytrace is unavailable and mock mode isn't allowed, return a neutral profile. + // Keytrace fetch failed — return a neutral "service unavailable" profile. return buildServiceUnavailableProfile(domain)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@server/api/keytrace/`[domain].ts around lines 194 - 195, The inline comment above the return to buildServiceUnavailableProfile(domain) references a non-existent "mock mode"; remove or correct that stale comment or, if mock behavior is intended, implement a real toggle and branch: either delete the "mock mode" phrase so the comment simply states the handler returns a neutral profile when Keytrace is unavailable, or add a boolean toggle (e.g., KEYTRACE_MOCK_ALLOWED or isKeytraceMockEnabled) and a conditional in the handler that returns a mock profile when enabled otherwise calls buildServiceUnavailableProfile(domain); update any tests accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/pages/profile/`[identity]/index.vue:
- Line 5: The explicit import of the composable useKeytraceProfile is redundant
because Nuxt auto-imports ~/composables/*; remove the import statement that
references useKeytraceProfile and rely on the globally available composable
where it's used (e.g., in the setup() or script block alongside other
auto-imported composables like useAtproto and useProfileLikes) to avoid
duplicate imports and lint warnings about unused imports.
In `@server/api/keytrace/reverify.post.ts`:
- Around line 17-26: mapReverifyStatus currently never returns 'stale' so a
reverify can incorrectly promote a stale claim to 'verified'; update
mapReverifyStatus to mirror the stale-detection used in mapVerificationStatus by
checking claim.lastVerifiedAt (or getOptionalClaimField(claim,
'lastVerifiedAt')) against the 30-day threshold and return 'stale' when
appropriate. Refactor by extracting a shared helper (e.g., isClaimStale or
computeVerificationStatus) that accepts a ClaimVerificationResult and the
freshness window, use that helper from both mapReverifyStatus and
mapVerificationStatus to ensure consistent mapping to
KeytraceReverifyResponse['status'] (including
'failed','stale','verified','unverified').
- Around line 67-71: The call to getClaimsForHandle in reverify.post.ts is not
wrapped in error handling, causing transient Keytrace outages to surface as HTTP
500; wrap the await getClaimsForHandle(identity) call in a try/catch, log the
error, and return the same "service unavailable" style response used elsewhere
(matching the pattern in the related [domain].ts handler) instead of letting the
error propagate; ensure the rest of the logic that uses result.claims,
mapPlatformToClaimType(platform), and matchesAccount(...) only runs on
successful retrieval.
- Around line 4-9: mapPlatformToClaimType currently only maps
'mastodon'→'activitypub' causing matchesAccount to miss platforms like
'bluesky'; update mapPlatformToClaimType to include the full platform→claimType
mapping used by server/api/keytrace/[domain].ts (e.g., bluesky→bsky,
mastodon→activitypub, etc.), or move the bidirectional map into a shared module
and import it here; extend mapReverifyStatus to return 'stale' when claims are
older than 30 days to mirror KeytraceVerificationStatus handling in [domain].ts;
and wrap the getClaimsForHandle call in a try/catch in reverify.post handler so
errors are caught and translated into the same client-facing responses used by
[domain].ts instead of leaking 500s.
---
Duplicate comments:
In `@app/pages/profile/`[identity]/index.vue:
- Around line 242-249: The Keytrace block (ProfileHeader + LinkedAccounts) is
rendered unconditionally because useKeytraceProfile(identity) returns fake
profiles for non-domain handles; wrap that <section> in a domain-check so it
only renders for domain-like identities. Add a small computed or helper (e.g.,
isDomainIdentity(identity) or computed property isDomain) that checks identity
format (e.g., contains a dot and not an at-prefix, or use a regex like
/^[^@]+\.[^@]+$/) and replace the unconditional section with <section
v-if="isDomain">…</section> (keeping existing props keytraceProfile,
keytraceLoading, keytraceAccounts and the useKeytraceProfile call intact).
Ensure the symbol names referenced are isDomain (or isDomainIdentity),
useKeytraceProfile, ProfileHeader, and LinkedAccounts so reviewers can locate
the change.
---
Nitpick comments:
In `@app/components/AccountItem.vue`:
- Around line 218-230: The formatDate function is hardcoded to 'en-US'; update
it to use the runtime locale from useI18n() (or accept a locale parameter) so
dates render per user language: inside formatDate (or its caller) obtain the
active locale from useI18n().locale.value and pass that to new
Intl.DateTimeFormat instead of 'en-US', keep the same options
(month/day/year/timeZone) and preserve the NaN check and 'Unknown date' return;
reference function name formatDate and the i18n hook useI18n to locate where to
change the code.
- Around line 14-44: This component hardcodes all user-visible copy (see
platformLabelMap, platformIconMap, proofMethodLabelMap, statusLabelMap,
verificationSteps and literal strings like "Unknown date", "Re-verification
failed: …", "Checking...", "Re-verify", "Re-verify Claim", "via {method}",
"Added", "Last checked"); update the component to use nuxt-i18n (useI18n or
this.$t) for every displayed label and message, replace the map values and
hard-coded strings with translation keys (e.g. account.platform.github,
account.proofMethod.pgp, account.status.verified, account.actions.reverify,
account.messages.unknownDate, etc.), and add those keys to the locale
schema/JSON used by sibling components so translations exist for all locales;
keep platformIconMap as-is (icons are not user copy) but ensure any user-facing
text derived from those maps uses translated labels.
In `@server/api/keytrace/`[domain].ts:
- Around line 194-195: The inline comment above the return to
buildServiceUnavailableProfile(domain) references a non-existent "mock mode";
remove or correct that stale comment or, if mock behavior is intended, implement
a real toggle and branch: either delete the "mock mode" phrase so the comment
simply states the handler returns a neutral profile when Keytrace is
unavailable, or add a boolean toggle (e.g., KEYTRACE_MOCK_ALLOWED or
isKeytraceMockEnabled) and a conditional in the handler that returns a mock
profile when enabled otherwise calls buildServiceUnavailableProfile(domain);
update any tests accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: eb707b47-0fc9-43d8-a2a2-e84c8f3af94a
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (8)
app/components/AccountItem.vueapp/components/LinkedAccounts.vueapp/components/ProfileHeader.vueapp/pages/profile/[identity]/index.vuepackage.jsonserver/api/keytrace/[domain].tsserver/api/keytrace/reverify.post.tsshared/types/keytrace.ts
✅ Files skipped from review due to trivial changes (2)
- package.json
- shared/types/keytrace.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- app/components/ProfileHeader.vue
- app/components/LinkedAccounts.vue
| import { updateProfile as updateProfileUtil } from '~/utils/atproto/profile' | ||
| import type { CommandPaletteContextCommandInput } from '~/types/command-palette' | ||
| import { getSafeHttpUrl } from '#shared/utils/url' | ||
| import { useKeytraceProfile } from '~/composables/useKeytraceProfile' |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check Nuxt config for composable auto-import and whether other composables are explicitly imported anywhere.
fd -e ts -e js 'nuxt.config' --max-depth 3 --exec cat {} \;
echo '---'
rg -nP "from ['\"]~/composables/" --type=vue --type=ts -g '!**/*.d.ts'Repository: npmx-dev/npmx.dev
Length of output: 12093
🏁 Script executed:
# Read the profile page to see current imports and composable usage
cat -n app/pages/profile/\[identity\]/index.vue | head -50Repository: npmx-dev/npmx.dev
Length of output: 1882
🏁 Script executed:
# Search for the mentioned composables in the profile page file
rg -n "useProfileLikes|useAtproto|useKeytraceProfile" app/pages/profile/\[identity\]/index.vueRepository: npmx-dev/npmx.dev
Length of output: 285
🏁 Script executed:
# Check if other composables are explicitly imported in the same file
rg -n "^import.*from.*composables" app/pages/profile/\[identity\]/index.vueRepository: npmx-dev/npmx.dev
Length of output: 132
Remove the explicit composable import — Nuxt auto-imports ~/composables/*.
The explicit import on line 5 is redundant given Nuxt's auto-import configuration. Other composables in this same file (useAtproto at line 29, useProfileLikes at line 83) are used without explicit imports, and this should follow the same pattern.
♻️ Proposed cleanup
import { getSafeHttpUrl } from '#shared/utils/url'
-import { useKeytraceProfile } from '~/composables/useKeytraceProfile'📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { useKeytraceProfile } from '~/composables/useKeytraceProfile' | |
| import { getSafeHttpUrl } from '#shared/utils/url' |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/pages/profile/`[identity]/index.vue at line 5, The explicit import of the
composable useKeytraceProfile is redundant because Nuxt auto-imports
~/composables/*; remove the import statement that references useKeytraceProfile
and rely on the globally available composable where it's used (e.g., in the
setup() or script block alongside other auto-imported composables like
useAtproto and useProfileLikes) to avoid duplicate imports and lint warnings
about unused imports.
| function mapPlatformToClaimType(platform: string): string { | ||
| if (platform === 'mastodon') { | ||
| return 'activitypub' | ||
| } | ||
| return platform | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Confirm which Keytrace claim types exist and the exact mapping used elsewhere.
rg -nP --type=ts -C2 "platformMap|platformToClaimType|bsky|activitypub"Repository: npmx-dev/npmx.dev
Length of output: 21536
🏁 Script executed:
# Read the complete reverify.post.ts file
cat -n server/api/keytrace/reverify.post.tsRepository: npmx-dev/npmx.dev
Length of output: 3416
🏁 Script executed:
# Check getClaimsForHandle usage in [domain].ts
sed -n '50,80p' server/api/keytrace/[domain].tsRepository: npmx-dev/npmx.dev
Length of output: 847
🏁 Script executed:
# Check KeytraceReverifyResponse type definition
rg -n "KeytraceReverifyResponse" --type=ts -A5Repository: npmx-dev/npmx.dev
Length of output: 1829
🏁 Script executed:
# Check how [domain].ts calls getClaimsForHandle and handles errors
grep -n "getClaimsForHandle" server/api/keytrace/[domain].ts -B2 -A5Repository: npmx-dev/npmx.dev
Length of output: 698
🏁 Script executed:
# Check KeytraceVerificationStatus type to see if 'stale' is valid
rg -n "type KeytraceVerificationStatus|enum KeytraceVerificationStatus" --type=ts -A10Repository: npmx-dev/npmx.dev
Length of output: 709
Platform mapping is incomplete — reverify will silently fail for non-mastodon platforms whose claim type differs from the platform label.
The sibling server/api/keytrace/[domain].ts exposes a comprehensive reverse map (lines 33–52). Here only mastodon → activitypub is handled, so accounts with platform: 'bluesky' (claim type bsky) never match in matchesAccount (line 34), and the endpoint always returns the "No matching Keytrace claim found" branch.
Additionally, mapReverifyStatus (lines 17–26) returns only 'failed', 'verified', or 'unverified'. The type KeytraceVerificationStatus includes 'stale' as a valid status, but reverify cannot return it — unlike [domain].ts, which detects claims verified over 30 days ago. This creates a UX inconsistency where a stale account appears verified after reverify.
Finally, getClaimsForHandle (line 67) is called without try/catch. Unlike [domain].ts (lines 142–143), errors are not caught and leak as 500s instead of appropriate client-facing signals.
Proposed fix for platform mapping
-function mapPlatformToClaimType(platform: string): string {
- if (platform === 'mastodon') {
- return 'activitypub'
- }
- return platform
-}
+const platformToClaimTypeMap: Record<string, string> = {
+ mastodon: 'activitypub',
+ bluesky: 'bsky',
+}
+
+function mapPlatformToClaimType(platform: string): string {
+ return platformToClaimTypeMap[platform] ?? platform
+}Consider extracting the bidirectional platform ↔ claim-type map into a shared module to prevent drift.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function mapPlatformToClaimType(platform: string): string { | |
| if (platform === 'mastodon') { | |
| return 'activitypub' | |
| } | |
| return platform | |
| } | |
| const platformToClaimTypeMap: Record<string, string> = { | |
| mastodon: 'activitypub', | |
| bluesky: 'bsky', | |
| } | |
| function mapPlatformToClaimType(platform: string): string { | |
| return platformToClaimTypeMap[platform] ?? platform | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@server/api/keytrace/reverify.post.ts` around lines 4 - 9,
mapPlatformToClaimType currently only maps 'mastodon'→'activitypub' causing
matchesAccount to miss platforms like 'bluesky'; update mapPlatformToClaimType
to include the full platform→claimType mapping used by
server/api/keytrace/[domain].ts (e.g., bluesky→bsky, mastodon→activitypub,
etc.), or move the bidirectional map into a shared module and import it here;
extend mapReverifyStatus to return 'stale' when claims are older than 30 days to
mirror KeytraceVerificationStatus handling in [domain].ts; and wrap the
getClaimsForHandle call in a try/catch in reverify.post handler so errors are
caught and translated into the same client-facing responses used by [domain].ts
instead of leaking 500s.
| function mapReverifyStatus(claim: ClaimVerificationResult): KeytraceReverifyResponse['status'] { | ||
| const rawStatus = getOptionalClaimField(claim, 'status') | ||
| if (rawStatus === 'failed' || rawStatus === 'retracted' || claim.error) { | ||
| return 'failed' | ||
| } | ||
| if (rawStatus === 'verified' || claim.verified) { | ||
| return 'verified' | ||
| } | ||
| return 'unverified' | ||
| } |
There was a problem hiding this comment.
mapReverifyStatus never returns 'stale', so reverify can "upgrade" a stale account to verified.
KeytraceReverifyResponse['status'] includes 'stale', and [domain].ts::mapVerificationStatus returns it when lastVerifiedAt is older than the 30-day threshold. This handler skips that check and collapses stale claims into 'verified', which:
- Contradicts the initial status the UI received from
GET /api/keytrace/[domain]. - Hides freshness problems from the user by resetting staleness on every re-verify click.
Please mirror the stale-detection logic from [domain].ts (ideally by extracting a shared helper) so both endpoints agree on the mapping:
🐛 Proposed fix
-function mapReverifyStatus(claim: ClaimVerificationResult): KeytraceReverifyResponse['status'] {
- const rawStatus = getOptionalClaimField(claim, 'status')
- if (rawStatus === 'failed' || rawStatus === 'retracted' || claim.error) {
- return 'failed'
- }
- if (rawStatus === 'verified' || claim.verified) {
- return 'verified'
- }
- return 'unverified'
-}
+function mapReverifyStatus(claim: ClaimVerificationResult): KeytraceReverifyResponse['status'] {
+ const rawStatus = getOptionalClaimField(claim, 'status')
+ const lastVerifiedAt = getOptionalClaimField(claim, 'lastVerifiedAt')
+
+ if (rawStatus === 'failed' || rawStatus === 'retracted') {
+ return 'failed'
+ }
+ if (rawStatus === 'verified') {
+ return isStaleIsoDate(lastVerifiedAt) ? 'stale' : 'verified'
+ }
+ if (claim.error) {
+ return 'failed'
+ }
+ if (claim.verified) {
+ return isStaleIsoDate(lastVerifiedAt) ? 'stale' : 'verified'
+ }
+ return 'unverified'
+}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@server/api/keytrace/reverify.post.ts` around lines 17 - 26, mapReverifyStatus
currently never returns 'stale' so a reverify can incorrectly promote a stale
claim to 'verified'; update mapReverifyStatus to mirror the stale-detection used
in mapVerificationStatus by checking claim.lastVerifiedAt (or
getOptionalClaimField(claim, 'lastVerifiedAt')) against the 30-day threshold and
return 'stale' when appropriate. Refactor by extracting a shared helper (e.g.,
isClaimStale or computeVerificationStatus) that accepts a
ClaimVerificationResult and the freshness window, use that helper from both
mapReverifyStatus and mapVerificationStatus to ensure consistent mapping to
KeytraceReverifyResponse['status'] (including
'failed','stale','verified','unverified').
| const result = await getClaimsForHandle(identity) | ||
| const claimType = mapPlatformToClaimType(platform) | ||
| const matchedClaim = result.claims.find(claim => | ||
| matchesAccount(claim, claimType, username, body?.url), | ||
| ) |
There was a problem hiding this comment.
getClaimsForHandle is not wrapped in error handling — transient Keytrace outages surface as HTTP 500.
[domain].ts already guards the same call with try/catch and falls back to a "service unavailable" response. This handler propagates the raw error, which the UI then shows as an opaque failure via getErrorMessage. As per coding guidelines ("Use error handling patterns consistently"), align the two endpoints.
🐛 Proposed fix
- const result = await getClaimsForHandle(identity)
- const claimType = mapPlatformToClaimType(platform)
- const matchedClaim = result.claims.find(claim =>
- matchesAccount(claim, claimType, username, body?.url),
- )
+ let result: Awaited<ReturnType<typeof getClaimsForHandle>>
+ try {
+ result = await getClaimsForHandle(identity)
+ } catch (error) {
+ console.error('[keytrace] reverify: getClaimsForHandle failed', error)
+ throw createError({
+ statusCode: 503,
+ message: 'Keytrace is temporarily unavailable. Please try again shortly.',
+ })
+ }
+
+ const claimType = mapPlatformToClaimType(platform)
+ const matchedClaim = result.claims.find(claim =>
+ matchesAccount(claim, claimType, username, body?.url),
+ )As per coding guidelines: "Use error handling patterns consistently".
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const result = await getClaimsForHandle(identity) | |
| const claimType = mapPlatformToClaimType(platform) | |
| const matchedClaim = result.claims.find(claim => | |
| matchesAccount(claim, claimType, username, body?.url), | |
| ) | |
| let result: Awaited<ReturnType<typeof getClaimsForHandle>> | |
| try { | |
| result = await getClaimsForHandle(identity) | |
| } catch (error) { | |
| console.error('[keytrace] reverify: getClaimsForHandle failed', error) | |
| throw createError({ | |
| statusCode: 503, | |
| message: 'Keytrace is temporarily unavailable. Please try again shortly.', | |
| }) | |
| } | |
| const claimType = mapPlatformToClaimType(platform) | |
| const matchedClaim = result.claims.find(claim => | |
| matchesAccount(claim, claimType, username, body?.url), | |
| ) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@server/api/keytrace/reverify.post.ts` around lines 67 - 71, The call to
getClaimsForHandle in reverify.post.ts is not wrapped in error handling, causing
transient Keytrace outages to surface as HTTP 500; wrap the await
getClaimsForHandle(identity) call in a try/catch, log the error, and return the
same "service unavailable" style response used elsewhere (matching the pattern
in the related [domain].ts handler) instead of letting the error propagate;
ensure the rest of the logic that uses result.claims,
mapPlatformToClaimType(platform), and matchesAccount(...) only runs on
successful retrieval.
🔗 Linked issue
resolves #2583
🧭 Context
This PR started as a UI POC. It is now updated to integrate real Keytrace claim data on /profile/{domain}.
📚 Description
What changed?
Verification
Notes